/*
 * Copyright 2015 泛泛o0之辈
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.jfast.framework.web.api;

import java.io.IOException;
import java.lang.reflect.*;
import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import cn.jfast.framework.base.util.*;
import cn.jfast.framework.jdbc.annotation.NoAutoLog;
import cn.jfast.framework.log.LogFactory;
import cn.jfast.framework.log.LogType;
import cn.jfast.framework.log.Logger;
import cn.jfast.framework.web.validate.ValidateEntity;
import cn.jfast.framework.web.validate.ValidateHandler;
import cn.jfast.framework.web.validate.ValidateSet;
import cn.jfast.framework.web.annotation.*;
import cn.jfast.framework.upload.FileRequestResolver;
import cn.jfast.framework.upload.UploadFile;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import net.sf.ezmorph.object.DateMorpher;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 
 */
public class ApiInvoker {

    private Logger log = LogFactory.getLogger(LogType.JFast, ApiLogger.class);
    /** */
    private Class<?> apiClass;
    /** */
    private Object api;
    /** */
    private Method apiMethod;
    /** */
    private HttpRequest request;
    /** */
    private HttpResponse response;
    /** */
    private Type[] paramTypes;
    /** */
    private String[] paramNames;
    /** */
    private Object[] arguments = new Object[0];
    /** */
    private java.text.DateFormat dateFormat;
    /** */
    private String apiUri;
    /** */
    private String reqUri;
    /** */
    private Object view;
    /** */
    private boolean preLoged = false;
    /** */
    private boolean afterLoged = false;
    /** */
    private boolean argumentsReady = false;
    /** */
    private NoAutoLog noAutoLog = null;
    /** */
    private ApiInvocation invocation;
    /** */
    private ValidateSet validateSet = new ValidateSet();
    /**  */
    private Map<String, Object> aopScopeParamMap = new HashMap<String, Object>();
    /** */
    private Map<String, String> uriScopeParamMap = new HashMap<String, String>();
    
    private String md5Key;
 
    public ApiInvocation getInvocation() {
		return invocation;
	}

	public void setInvocation(ApiInvocation invocation) {
		this.invocation = invocation;
	}

	public Logger getLog() {
		return log;
	}

	public void setLog(Logger log) {
		this.log = log;
	}

	public HttpRequest getRequest() {
		return request;
	}
	
	public void setRequest(HttpRequest request) {
		this.request = request;
	}

	public Type[] getParamTypes() {
		return paramTypes;
	}

	public void setParamTypes(Type[] paramTypes) {
		this.paramTypes = paramTypes;
	}

	public String[] getParamNames() {
		return paramNames;
	}

	public void setParamNames(String[] paramNames) {
		this.paramNames = paramNames;
	}

	public java.text.DateFormat getDateFormat() {
		return dateFormat;
	}

	public void setDateFormat(java.text.DateFormat dateFormat) {
		this.dateFormat = dateFormat;
	}

	public Object getResult() {
		return view;
	}

	public void setResult(Object result) {
		this.view = result;
	}

	public boolean isLoged() {
		return preLoged;
	}

	public void setLoged(boolean isLoged) {
		this.preLoged = isLoged;
	}

	public Map<String, Object> getAopAttr() {
		return aopScopeParamMap;
	}

	public void setAopAttr(Map<String, Object> aopAttr) {
		this.aopScopeParamMap = aopAttr;
	}

	public Map<String, String> getDynamicReqRouteParamMap() {
		return uriScopeParamMap;
	}

	public void setDynamicReqRouteParamMap(Map<String, String> dynamicReqRouteParamMap) {
		this.uriScopeParamMap = dynamicReqRouteParamMap;
	}
	
	public String getParameter(String paramName){
		return uriScopeParamMap.get(paramName);
	}

	public Class<?> getApiClass() {
		return apiClass;
	}

	public Method getApiMethod() {
		return apiMethod;
	}

	public HttpResponse getResponse() {
		return response;
	}

	public String getApiRoute() {
		return apiUri;
	}

	public String getReqRoute() {
		return reqUri;
	}

	public void setArguments(Object[] arguments) {
		this.arguments = arguments;
	}

    public void setApiRoute(String apiRoute) {
        this.apiUri = apiRoute;
    }

    public void setReqRoute(String reqRoute) {
        this.reqUri = reqRoute;
    }

    public void setApiClass(Class<?> apiClass) {
        this.apiClass = apiClass;
    }
    
    /**  */
    public void addAopScopeParam(String paramName, Object paramValue) {
        if(argumentsReady && null != paramNames) {
	        for(int i = 0;i < paramNames.length; i++){ 
	        	if(paramNames[i].equals(paramName)){
	        		arguments[i] = paramValue;
	        	}
	        }
        }
        this.aopScopeParamMap.put(paramName, paramValue);
    }

    public void setApi(Object apiObject) {
    	Assert.notNull(apiObject);
        this.api = apiObject;
        this.apiClass = apiObject.getClass();
    }

    public Object getApi() {
        return this.api;
    }

    public void setResponse(HttpResponse response) {
        this.response = response;
    }

    public void setApiMethod(Method apiMethod) {
        this.apiMethod = apiMethod;
    }
    
    private Object getParam(String paramName){
    	if (aopScopeParamMap.containsKey(paramName)) {
           return aopScopeParamMap.get(paramName);
        } else if (uriScopeParamMap.containsKey(paramName)) {
            return uriScopeParamMap.get(paramName);
        } else {
        	return request.getParameter(paramName);
        }
    } 
    
    public void prepareApiArguments() throws NotFoundException, ParseException, IllegalAccessException, InstantiationException, IOException, ClassNotFoundException {
        argumentsReady = true;
    	ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(new ClassClassPath(ApiInvoker.class));
        CtClass clazz = cp.getCtClass(apiClass.getName());
        CtMethod method = clazz.getDeclaredMethod(apiMethod.getName());
        String paramName;
        Type paramType;
        paramTypes = apiMethod.getParameterTypes();
        paramNames = ClassUtils.getMethodParamNames(method);
        if(null == paramNames)
            paramNames = new String[paramTypes.length];
        arguments = new Object[paramTypes.length];
        Object[][] annotations = method.getAvailableParameterAnnotations();   
        for (int i = 0; i < paramTypes.length; i++) {
            HttpParam httpParam = null;
            dateFormat = null;
            paramType = paramTypes[i];
            paramName = paramNames[i];
            if(null == paramName)
                paramName = "";               
            // 获取方法参数的属性
            for (Object anno : annotations[i]) {
                if (anno instanceof HttpParam) {
                    httpParam = (HttpParam) anno;
                    if(StringUtils.isNotEmpty(httpParam.name())){
                    	paramName = httpParam.name();
                    	paramNames[i] = paramName;
                    }
                }
            }
            //从拦截器中获取参数
            if (aopScopeParamMap.containsKey(paramName)) {
                arguments[i] = aopScopeParamMap.get(paramName);
                addValidate(httpParam,paramName,arguments[i]);
                continue;
            }
            //从请求路径中取参数
            if (uriScopeParamMap.containsKey(paramName)) {
                if (paramType == String.class) {
                    arguments[i] = uriScopeParamMap.get(paramName);
                } else if (paramType == Integer.TYPE || paramType == Integer.class) {
                    arguments[i] = Integer.parseInt(uriScopeParamMap.get(paramName));
                } else if (paramType == Long.TYPE || paramType == Long.class) {
                    arguments[i] = Long.parseLong(uriScopeParamMap.get(paramName));
                } else if (paramType == Float.TYPE || paramType == Float.class) {
                    arguments[i] = Float.parseFloat(uriScopeParamMap.get(paramName));
                } else if (paramType == Double.TYPE || paramType == Double.class) {
                    arguments[i] = Double.parseDouble(uriScopeParamMap.get(paramName));
                } else if (paramType == Short.TYPE || paramType == Short.class) {
                    arguments[i] = Short.parseShort(uriScopeParamMap.get(paramName));
                } else if (paramType == Character.TYPE || paramType == Character.class) {
                	String chars = uriScopeParamMap.get(paramName);
                	if( null == chars || chars.equals(""))
                		arguments[i] = null;
                	else if( chars.length() > 1)
                		throw new IllegalArgumentException("参数 ["+paramName+"]长度超出范围");
                	else
                		arguments[i] = chars.toCharArray()[0];
                } else if (paramType == Short.TYPE || paramType == Short.class) {
                    arguments[i] = Short.parseShort(uriScopeParamMap.get(paramName));
                } else if (paramType == Boolean.TYPE || paramType == Boolean.class) {
                	arguments[i] = Boolean.parseBoolean(uriScopeParamMap.get(paramName));
                } else {
                    throw new IllegalArgumentException("路径参数不可以为" + paramType + "类型");
                }
                addValidate(httpParam,paramName,arguments[i]);
                continue;
            }

            //从普通请求域获取参数
            if (paramType == HttpServletRequest.class) {
                arguments[i] = request.getRequest();
            } else if (paramType == HttpServletResponse.class) {
                arguments[i] = response;
            } else if (paramType == HttpSession.class) {
                arguments[i] = request.getRequest().getSession();
            } else if (paramType == Cookie[].class) {
                arguments[i] = request.getRequest().getCookies();
            } else {
                arguments[i] = getParamValue(paramType, paramName, httpParam);
            }
            addValidate(httpParam,paramName,arguments[i]);
        }
    }
    
    public void addValidate(HttpParam httpParam,String paramName,Object paramValue) throws InstantiationException, IllegalAccessException{
    	if(ObjectUtils.isNotNull(httpParam)){
    		ValidateHandler valid;
            for(String validate: httpParam.validate()){
            	if("".equals(validate))
            		continue;
            	valid = ApiContext.getValidate(validate);
            	Assert.notNull(valid,String.format("找不到适配的验证器:%s",validate));
            	validateSet.add(new ValidateEntity((ValidateHandler)ApiContext.fillResource(valid), paramName, paramValue));
            }
        }
    }
    /**
     * 获取复杂类型对象参数值
     *
     * @param paramType
     * @param paramName
     * @param httpParam
     * @return
     * @throws IllegalAccessException
     * @throws ParseException
     * @throws InstantiationException
     */
    private Object getParamValue(Type paramType, String paramName,
                                 HttpParam httpParam) throws IllegalAccessException, ParseException, InstantiationException {
        String tempParamName = paramName;
        Object paramValue = null;
        if (ClassUtils.isCommonTypeOrWrapper(((Class<?>) paramType))
                || paramType == UploadFile.class) {
            paramValue = primitiveValue(paramType, paramName, httpParam);
        } else if(((Class<?>) paramType).isArray()){
            if (ObjectUtils.isNotNull(httpParam) && httpParam.fromJson()) {
                paramValue = jsonParamValue(paramType, paramName, httpParam);
            } else {
                paramValue = arrayParamValue(paramType, paramName, httpParam);
            }
        }else {
            if (ObjectUtils.isNotNull(httpParam) && httpParam.fromJson()) {
                paramValue = jsonParamValue(paramType, paramName, httpParam);
            } else {
                for (Field field : ReflectionUtils
                        .getFields((Class<?>) paramType)) {
                    field.setAccessible(true);
                    paramName = tempParamName + "." + field.getName();
                    paramType = field.getType();
                    Object value = primitiveValue(paramType, paramName, httpParam);
                    if (null != value){
                    	if(null == paramValue)
                    		paramValue = ((Class<?>) paramType).newInstance();
                        field.set(paramValue, value);
                    }
                }
            }
        }
        return paramValue;
    }

    /**
     * 获得Json类型参数对应的参数值
     *
     * @param paramType
     * @param paramName
     * @param httpParam
     * @return
     */
    private Object jsonParamValue(Type paramType, String paramName,
                                     HttpParam httpParam){
    	
        Object value = null;
        if(StringUtils.isNotEmpty(request.getParameter(paramName).trim())) {
        	   String[] pattern = new String[]{httpParam.datePattern()};
            if (((Class<?>) paramType).isArray()) {
                JSONArray jsonArray = JSONArray.fromObject(request.getParameter(paramName));
                Class<?> arrayInnerType = null;
				try {
					arrayInnerType = Class.forName(((Class<?>)paramType).getName().substring(2).replaceFirst(";", ""));
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
				JSONUtils.getMorpherRegistry().registerMorpher(new DateMorpher(pattern));
                value = JSONArray.toArray(jsonArray,arrayInnerType);
            } else {
                JSONObject jsonObject = JSONObject.fromObject(request.getParameter(paramName));
                JSONUtils.getMorpherRegistry().registerMorpher(new DateMorpher(pattern));
                @SuppressWarnings("unchecked")
				Set<String> set = jsonObject.keySet();
                Iterator<String> ite = set.iterator();
                while(ite.hasNext()){
                	String key = ite.next();
                	if(StringUtils.isEmpty(jsonObject.getString(key))){
                		jsonObject.remove(key);
                	}
                }
                value = JSONObject.toBean(jsonObject,(Class<?>)paramType);
            }
        }
        return value;
    }

    /**
     * 获得集合数组类型参数对应的参数值
     *
     * @param paramType
     * @param paramName
     * @param httpParam
     * @return
     */
    private Object arrayParamValue(Type paramType, String paramName,
                                 HttpParam httpParam){
    	if(ObjectUtils.isNotNull(httpParam))
    		dateFormat = new SimpleDateFormat(httpParam.datePattern());
        Object value = null;
        String paramValues[] = request.getParameters(paramName);
        if (null != paramValues || !request.getUploadFile(paramName).isEmpty()) {
            if (paramType == java.util.Date[].class) {
            	Assert.notNull(httpParam, "参数" + paramName + "必须声明@Param且在@Param注解中注明日期解析模式");
                Assert.notEmpty(httpParam.datePattern(), "参数 [" + paramName + " ]必须在@Param注解中注明日期解析模式");
                java.util.Date[] dates  = new java.util.Date[paramValues.length];
                for(int i= 0; i < paramValues.length; i++){
                    try {
                    	if(StringUtils.isNotEmpty(paramValues[i]))
                    		dates[i] = dateFormat.parse(paramValues[i]);
                    } catch (ParseException e) {
                        dates[i] = null;
                        throw new IllegalArgumentException("参数  ["+paramValues[i]+"] 不能被解析为日期：");
                    }
                }
                value = dates;
            } else if (paramType == UploadFile[].class) {
                Assert.isTrue(FileRequestResolver.isMultipart(request.getRequest()),
                        "上传文件时,表单属性enctype必须为multipart/form-data");
                if(null != request.getUploadFile(paramName))
                	value = request.getUploadFile(paramName).toArray(new UploadFile[]{});
            } else if (paramType == Integer[].class) {
                value = StringUtils.strArr2IntArr(paramValues);
            } else if (paramType == Short[].class) {
                value = StringUtils.strArr2ShortArr(paramValues);
            } else if (paramType == Float[].class) {
                value = StringUtils.strArr2FloatArr(paramValues);
            } else if (paramType == Long[].class) {
                value = StringUtils.strArr2LongArr(paramValues);
            } else if (paramType == Double[].class) {
                value = StringUtils.strArr2DoubleArr(paramValues);
            } else if (paramType == String[].class) {
                value = paramValues;
            } else {
            	throw new IllegalArgumentException("不支持该参数类型："+paramType);
            }
        }
        return value;
    }

    /**
     * 获得简单类型参数对应参数值
     *
     * @param paramType
     * @param paramName
     * @param httpParam
     * @return
     * @throws ParseException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private Object primitiveValue(Type paramType, String paramName,
                                     HttpParam httpParam) throws ParseException, IllegalAccessException, InstantiationException {
    	if(ObjectUtils.isNotNull(httpParam))
    		dateFormat = new SimpleDateFormat(httpParam.datePattern());
        Object value = null;
        if (null != getParam(paramName)
                || hasExtendParam(paramName)
                || null != request.getUploadFile(paramName)) {
            String tempVal = String.valueOf(getParam(paramName));
            if(StringUtils.isEmpty(tempVal) && null == request.getUploadFile(paramName))
            	return null;
            if (paramType == Integer.TYPE || paramType == Integer.class) {
                value = Integer.parseInt(tempVal);
            } else if (paramType == Short.TYPE || paramType == Short.class) {
                value = Short.parseShort(tempVal);
            } else if (paramType == Float.TYPE || paramType == Float.class) {
                value = Float.parseFloat(tempVal);
            } else if (paramType == Long.TYPE || paramType == Long.class) {
                value = Long.parseLong(tempVal);
            } else if (paramType == Double.TYPE || paramType == Double.class) {
                value = Double.parseDouble(tempVal);
            } else if (paramType == Boolean.TYPE || paramType == Boolean.class) {
                value = Boolean.parseBoolean(tempVal);
            } else if (paramType == Character.TYPE || paramType == Character.class) {
            	if( null == tempVal || tempVal.equals("") )
            		value = null;
            	else if( tempVal.length() > 1)
            		throw new IllegalArgumentException("参数 ["+paramName+"]长度超出范围");
            	else
            		value = tempVal.toCharArray()[0];
            } else if (paramType == String.class) {
                value = tempVal;
            } else if (paramType == UploadFile.class) {
                Assert.isTrue(FileRequestResolver.isMultipart(request.getRequest()),
                        "上传文件时,表单属性enctype必须为multipart/form-data.");
                Assert.isTrue(request.getUploadFile(paramName).size() <= 1,
                        "文件数量超出范围:"+request.getUploadFile(paramName).size());
                if(request.getUploadFile(paramName).size() == 1)
                	value = request.getUploadFile(paramName).get(0);
                else
                	value = null;
            } else if (paramType == Date.class || paramType == java.util.Date.class) {
            	Assert.notNull(httpParam, "参数" + paramName + "必须声明@Param且在@Param注解中注明日期解析模式");
                Assert.notEmpty(httpParam.datePattern(), "参数 [" + paramName + " ]必须在@Param注解中注明日期解析模式");
                if(StringUtils.isNotEmpty(tempVal))
                	try{
                		value = dateFormat.parse(tempVal);
                	} catch(ParseException e){
                		throw new IllegalArgumentException("参数  ["+tempVal+"] 不能被解析为日期：");
                	}
            } else {
            	throw new IllegalArgumentException("不支持该参数类型："+paramType);
            }
        }
        return value;
    }

    /**
     * 判断是否有对应子参数名
     * @param paramName
     * @return
     */
    private boolean hasExtendParam(String paramName) {
        boolean hasExtendParam = false;
        Enumeration<String> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements())
            if (paramNames.nextElement().indexOf(paramName) != -1) {
            	hasExtendParam = true;
                break;
            }
        return hasExtendParam;
    }

    /**
     * Api对应方法执行操作
     *
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws NotFoundException
     * @throws ParseException
     * @throws ClassNotFoundException
     * @throws IOException
     * @throws InstantiationException
     */
    public Object invoke() throws InvocationTargetException,
            IllegalAccessException,
            NotFoundException, ParseException, ClassNotFoundException, IOException, InstantiationException {
        ReflectionUtils.makeAccessible(apiMethod); 
        preLog();
        if(validate()){
        	view = apiMethod.invoke(api, arguments);
        }
        afterLog();
        return view;
    }

    private boolean validate() {
    	boolean overValidate = true;
    	while(validateSet.hasNext()){
    		ValidateEntity entity = validateSet.next();
    		ValidateHandler handler = entity.getValidate();
    		try {
    			handler.handleValidate(invocation, entity.getParamName(), entity.getParamValue());
			} catch (Exception e) {
				this.invocation.addException(e);
				handler.handleError(invocation, e);
				overValidate = false;
			}
    	}
    	return overValidate;
	}

	public void preLog() throws NotFoundException, ParseException, IllegalAccessException, InstantiationException, IOException, ClassNotFoundException {
    	synchronized (this) {
	    	if(!preLoged){
	    		if(!argumentsReady){
	    			try{
	    				prepareApiArguments();
	    			} catch(IllegalArgumentException e){
	    				throw e;
	    			} finally {
	    				if(null == noAutoLog){
	    					noAutoLog = apiMethod.getAnnotation(NoAutoLog.class);
	    				}
	    				if(null == noAutoLog){
		    				md5Key = EncryptUtils.md5Encrypt(new java.util.Date().getTime()+""+apiClass.getName()+apiMethod.getName());
		    				log.info("\n%s	:	------------------ %s ------------------\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n" +
			                        "%s	:	%s\n ",
			                "接收请求", md5Key,
			                "请求地址", request.getRequestURI(),
			                "请求方式", request.getMethod(),
			                "匹配路径", apiUri,
			                "服务对象", api.getClass().getName() + "." + apiMethod.getName() + "()",
			                "参数名称", "[" + StringUtils.join(paramNames, ",") + "]",
			                "传入参数", "[" + StringUtils.join(arguments, ",") + "]",
			                "拦截器链", invocation.getAopNames(),
			                "验证器链", validateSet.getValidateNames());
	    				}
	    				preLoged = true;
	    			}
	    		} 
	    	}
    	}
    }

    public void afterLog() {
    	afterLog(view);
    }
    
    public void afterLog(Object view) {
    	synchronized (this) {
    		if(!afterLoged){
    			if(null == noAutoLog){
					noAutoLog = apiMethod.getAnnotation(NoAutoLog.class);
				}
				if(null == noAutoLog){
			    	log.info("\n%s	:	------------------ %s ------------------\n" +
			             "%s	:	%s\n ",
			             "响应请求", md5Key,
			             "返回视图", null == view ? "" : view.toString());
			    	afterLoged = true;
				}
    		}
    	}
    }

    /**
     * 获得远程IP地址
     *
     * @param request
     * @return
     */
    public static String getIpAddr(HttpRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
    
    public void init(){
        if (!apiUri.equals(reqUri)) {
            String[] dynamicPaths = apiUri.replaceAll("/+", "/").split("/");
            String[] reqPaths = reqUri.replaceAll("/+", "/").split("/");
            for (int i = 0; i < dynamicPaths.length; i++) {
                if (dynamicPaths[i].startsWith(":")) {
                    uriScopeParamMap.put(dynamicPaths[i].substring(1), reqPaths[i]);
                }
            }
        }
    }
    
}
